﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Unicode;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Serialization;

namespace StableDiffusionTools.Framework
{
    public class SettingsManager<T> where T : Settings, new()
    {
        private static readonly JsonSerializerOptions options = new JsonSerializerOptions() { WriteIndented = true, Encoder = JavaScriptEncoder.Create(UnicodeRanges.All) };
        private static readonly JsonReaderOptions readerOptions = new JsonReaderOptions();
        private static readonly JsonWriterOptions writerOptions = new JsonWriterOptions() { Indented = true, Encoder = JavaScriptEncoder.Create(UnicodeRanges.All) };
        private static readonly XmlReaderSettings readerSettings = new XmlReaderSettings();
        private static readonly XmlWriterSettings writerSettings = new XmlWriterSettings { Indent = true, OmitXmlDeclaration = true };

        public string DefaultFileName { get; set; }
        public string DefaultExtension { get; set; }
        public SettingsFormat Format { get; set; }
        public bool CreateBackups { get; set; }
        public string Directory { get; set; } = "";
        //------------------
        //Constructor
        //------------------
        public SettingsManager(string defaultFileName = "Settings", string defaultExtension = ".cfg", SettingsFormat defaultFormat = SettingsFormat.XML, bool createBackups = true)
        {
            DefaultFileName = defaultFileName;
            DefaultExtension = defaultExtension;
            Format = defaultFormat;
            CreateBackups = createBackups;
        }

        //------------------
        //HasSettings
        //------------------
        public bool HasSettings(string settingsName = "", string extension = "")
        {
            if (string.IsNullOrEmpty(settingsName)) settingsName = DefaultFileName;
            if (string.IsNullOrEmpty(extension)) extension = DefaultExtension;

            return File.Exists(GetSettingsPath(settingsName, extension));
        }

        //------------------
        //TryLoadSettings
        //------------------
        public bool TryLoadSettings(out T settingsResult, string settingsName = "", string extension = "")
        {
            bool result = false;

            if (string.IsNullOrEmpty(settingsName)) settingsName = DefaultFileName;
            if (string.IsNullOrEmpty(extension)) extension = DefaultExtension;
            settingsResult = LoadSettings(settingsName, extension);
            if (settingsResult != null)
                result = true;

            return result;
        }
        //------------------
        //CreateSettings
        //------------------
        private T CreateSettings()
        {
            T newSetting = new T();
            return newSetting;
        }

        //------------------
        //LoadSettings
        //------------------
        public T LoadSettings(string settingsName = "", string extension = "", T defaultSettings = null)
        {
            T result = null;

            if (string.IsNullOrEmpty(settingsName)) settingsName = DefaultFileName;
            if (string.IsNullOrEmpty(extension)) extension = DefaultExtension;

            string loadPath = GetSettingsPath(settingsName, extension);
            try
            {
                if (File.Exists(loadPath))
                {
                    if (Format == SettingsFormat.XML)
                        result = LoadSettingsXML(loadPath);
                    else if (Format == SettingsFormat.JSON)
                        result = LoadSettingsJSON(loadPath);
                }
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(ex.Message);
            }

            if(result == null)
                result = defaultSettings ?? CreateSettings();

            result.EndInit();

            return result;
        }

        private T LoadSettingsXML(string loadPath)
        {
            T result = null;
            var serializer = new XmlSerializer(typeof(T));
            using (var reader = new StreamReader(loadPath))
            using (var xmlReader = XmlReader.Create(reader, readerSettings))
            {
                result = (T)serializer.Deserialize(xmlReader);
            }
            return result;
        }

        private T LoadSettingsJSON(string loadPath)
        {
            T result = null;
            var utf8Reader = new Utf8JsonReader(new ReadOnlySpan<byte>(File.ReadAllBytes(loadPath)), readerOptions);
            result = JsonSerializer.Deserialize<T>(ref utf8Reader, options);
            return result;
        }

        //------------------
        //SaveSettings
        //------------------
        public void SaveSettings(T settings, string settingsName = "", string extension = "")
        {
            if (string.IsNullOrEmpty(settingsName)) settingsName = DefaultFileName;
            if (string.IsNullOrEmpty(extension)) extension = DefaultExtension;

            BackupOrRemoveSettings(settingsName, extension);

            string savePath = GetSettingsPath(settingsName, extension);
            if (Format == SettingsFormat.XML)
                SaveSettingsXML(settings, savePath);
            else if (Format == SettingsFormat.JSON)
                SaveSettingsJSON(settings, savePath);
        }

        private void SaveSettingsXML(T settings, string savePath)
        {
            var emptyNamespace = new XmlSerializerNamespaces(new[] { XmlQualifiedName.Empty });
            var serializer = new XmlSerializer(typeof(T));
            using (var writer = new StreamWriter(savePath, false))
            using (var xmlWriter = XmlWriter.Create(writer, writerSettings))
            {
                serializer.Serialize(xmlWriter, settings, emptyNamespace);
            }
        }

        private void SaveSettingsJSON(T settings, string savePath)
        {
            using (var stream = new FileStream(savePath, FileMode.Create))
            using (var writer = new Utf8JsonWriter(stream, writerOptions))
            {
                JsonSerializer.Serialize(writer, settings, options);
            }
        }

        //------------------
        //RemoveSettings
        //------------------
        public void RemoveSettings(string settingsName = "", string extension = "")
        {
            if (string.IsNullOrEmpty(settingsName)) settingsName = DefaultFileName;
            if (string.IsNullOrEmpty(extension)) extension = DefaultExtension;

            BackupOrRemoveSettings(settingsName, extension);
        }

        //------------------
        //ListSettings
        //------------------
        public List<string> ListSettings(string extension = "")
        {
            if (string.IsNullOrEmpty(extension))
                extension = DefaultExtension;

            List<string> result = new List<string>();
            DirectoryInfo dir = new DirectoryInfo(Path.Combine(GetApplicationDirectory(), Directory ?? ""));
            foreach (FileInfo file in dir.EnumerateFiles())
            {
                if (!extension.Equals(file.Extension, StringComparison.InvariantCultureIgnoreCase))
                    continue;

                result.Add(file.Name.Remove(file.Name.LastIndexOf(file.Extension)));
            }
            return result;
        }

        //------------------
        //Private methods
        //------------------
        private void BackupOrRemoveSettings(string settingsName, string extension)
        {
            string savePath = GetSettingsPath(settingsName, extension);
            string savePathBackup = GetSettingsBackupPath(settingsName, extension);
            if (File.Exists(savePath))
            {
                if (File.Exists(savePathBackup))
                    File.Delete(savePathBackup);

                if (CreateBackups)
                {
                    File.Move(savePath, savePathBackup);
                }
                else
                {
                    File.Delete(savePath);
                }
            }
        }

        private string GetApplicationDirectory()
        {
            return Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
        }

        private string GetSettingsPath(string settingsName, string extension = "")
        {
            if (string.IsNullOrEmpty(extension))
                extension = DefaultExtension;

            return Path.Combine(GetApplicationDirectory(), Directory ?? "", settingsName + extension);
        }

        private string GetSettingsBackupPath(string settingsName, string extension = "")
        {
            return Path.Combine(GetApplicationDirectory(), Directory ?? "", settingsName + extension + ".bak");
        }
    }

    //[XmlElement(Type = typeof(XmlColor))]
    public class XmlColor
    {
        [XmlIgnore]
        public Color Color = Color.Black;

        [XmlAttribute]
        public string Web
        {
            get { return ColorTranslator.ToHtml(Color); }
            set
            {
                try
                {
                    if (Alpha == 0xFF) // preserve named color value if possible
                        Color = ColorTranslator.FromHtml(value);
                    else
                        Color = Color.FromArgb(Alpha, ColorTranslator.FromHtml(value));
                }
                catch (Exception)
                {
                    Color = Color.Black;
                }
            }
        }

        [XmlAttribute]
        public byte Alpha
        {
            get { return Color.A; }
            set
            {
                if (value != Color.A) // avoid hammering named color if no alpha change
                    Color = Color.FromArgb(value, Color);
            }
        }
        public bool ShouldSerializeAlpha() { return Alpha < 0xFF; }

        public XmlColor() { }
        public XmlColor(Color c) { Color = c; }

        public static implicit operator Color(XmlColor x)
        {
            return x.Color;
        }

        public static implicit operator XmlColor(Color c)
        {
            return new XmlColor(c);
        }
    }
}
